/*
 * Copyright (C) 2009 WorkSmart Labs, Inc.
 */

package com.wsl.HealthChallenge.utils;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.Calendar;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.CDATASection;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * Utility class that provides helper function for parsing XML using the org.w3c.dom package.
 * @author mark
 *
 */
public class DomXmlUtils {
    /**
     * Fetches the first CDATASection inside the one element
     * with the given name tag inside the given parent element.
     * @param parent
     * @param name
     * @return
     */
    public static CDATASection getCDATASection(Element parent, String name) {
        Element element = getElement(parent, name);
        if (element == null) {
            return null;
        }

        NodeList nodeLst = element.getChildNodes();
        for (int i = 0; i < nodeLst.getLength(); i++) {
            Node node = nodeLst.item(i);
            if (node.getNodeType() == Node.CDATA_SECTION_NODE) {
                return (CDATASection) node;
            }
        }

        // TODO(artem): Restore the assert below once we migrate
        //              AssertionUtils into a common folder.
        // AssertionUtils.assertError();
        return null;
    }

    /**
     * Fetches the one and only child element with the given name in the given element,
     * or null if it does not exists.
     * @param parent
     * @param elementName
     * @return
     */
    public static Element getElement(Element parent, String elementName) {
        Node node = getNode(parent, elementName);
        if (node!= null && node.getNodeType() == Node.ELEMENT_NODE) {
            return (Element) node;
        } else {
            return null;
        }
    }

    public static Element getElementFromPath(Element parent, String path) {
        String[] elementNames = path.split("/");
        Element node = parent;
        for (int i = 0; i < elementNames.length; i++) {
            if (node != null) {
                node = getElement(node, elementNames[i]);
            }
        }
        return node;
    }

    /**
     * Fetches the one and only child node with the given name in the given element,
     * or null if it does not exists.
     * @param parent
     * @param nodeName
     * @return
     */
    public static Node getNode(Element parent, String nodeName) {
        NodeList nodeLst = parent.getElementsByTagName(nodeName);
        // TODO(artem): Restore the assert below once we migrate DebugUtils into a common folder.
        // DebugUtils.assertTrue(nodeLst.getLength() == 1);
        if (nodeLst.getLength() > 0) {
            return nodeLst.item(0);
        } else {
            return null;
        }
    }

    /**
     * Reads a tag containing a CDATASection from the given element.
     * @param el
     * @param name
     * @return
     */
    public static String readCDATASection(Element el, String name) {
        CDATASection cdataSection = getCDATASection(el, name);
        if (cdataSection != null){
            return cdataSection.getData();
        } else {
            // This is a quick fix to stop the highscore from crashing because the comment string
            // is null. This seemed to start since froyo.
            // TODO(mark): Find out why exactly this is sometimes null since we expect that there
            // is always at least an empty CDATA# section. 
            return "";
        }
    }

    /**
     * Parses the input XML stream and returns the Document.
     * @param instream
     * @return
     * @throws ParserConfigurationException
     * @throws SAXException
     * @throws IOException
     */
    public static Document parseDocumentFromStream(InputStream instream) {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db = null;
        Document doc = null;

        try {
            db = dbf.newDocumentBuilder();
            doc = db.parse(instream);
            doc.getDocumentElement().normalize();
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return doc;
    }

    /**
     * Parses the XML encoded string and returns the Document.
     * TODO(mark): Share more code with parseDocumentFromStream().
     * @param xmlString
     * @return
     */
    public static Document parseDocumentFromString(String xmlString) {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db = null;
        Document doc = null;
        try {
            db = dbf.newDocumentBuilder();
            StringReader reader = new StringReader(xmlString);
            doc = db.parse(new InputSource(reader));
            doc.getDocumentElement().normalize();
            reader.close();
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return doc;
    }
    
    /**
     * Parses the specified attribute into a Calendar.
     * @param attributeName String name of the attribute
     * @param childElement Element attribute
     * @return parsed Calendar
     */
    public static Calendar parseCalendarTimeAttribute(String attributeName, Element childElement) {
        String expectedStartTimeString = childElement.getAttribute(attributeName);
        Calendar expectedStartTime = Calendar.getInstance();
        expectedStartTime.setTimeInMillis(new Long(expectedStartTimeString));
        return expectedStartTime;
    }

    /**
     * Parses the specified attribute into an int.
     * @param attributeName String name of the attribute
     * @param childElement Element attribute
     * @return parsed int
     */
    public static int parseIntAttribute(String attributeName, Element childElement) {
        String showReminderBeforeMinutesString = childElement.getAttribute(attributeName);
        int showReminderBeforeMinutes = new Integer(showReminderBeforeMinutesString);
        return showReminderBeforeMinutes;
    }

    /**
     * Parses the specified attribute into a double.
     * @param attributeName String name of the attribute
     * @param childElement Element attribute
     * @return parsed double
     */    
    public static double parseDoubleAttribute(String attributeName, Element childElement) {
        String expectedDistanceMetersString = childElement.getAttribute(attributeName);
        double expectedDistanceMeters = new Double(expectedDistanceMetersString);
        return expectedDistanceMeters;
    }

    /**
     * Parses the specified attribute into a long.
     * @param attributeName String name of the attribute
     * @param childElement Element attribute
     * @return parsed long
     */
    public static long parseLongAttribute(String attributeName, Element childElement) {
        String expectedDurationMillisString = childElement.getAttribute(attributeName);
        long expectedDurationMillis = new Long(expectedDurationMillisString);
        return expectedDurationMillis;
    }
}